package cn.kgc.commons.advice;

import cn.kgc.commons.annotation.MyCacheable;
import cn.kgc.commons.util.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.lang.reflect.Method;

/**
 * @description:
 * @author: 石浩杰
 * @date: 2022-12-12 10:59
 */

@Aspect
@Slf4j
@Component
public class MyCacheableAdvice {

    @Resource
    private RedisUtil redisUtil;

    @Resource
    private RedissonClient redissonClient;
    @Pointcut("@annotation(cn.kgc.commons.annotation.MyCacheable)")
    public void myCacheablePointCut() {}

    @Around(value = "myCacheablePointCut()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //1 判断方法上是否有MyCacheable注解
        MethodSignature methodSignature=(MethodSignature) proceedingJoinPoint.getSignature();
        Method method=methodSignature.getMethod();
        //获得方法上的缓存注解信息
        if (method.isAnnotationPresent(MyCacheable.class)){
            MyCacheable myCacheable=method.getAnnotation(MyCacheable.class);
            //SpEL表达式
            String cacheKeyPress= myCacheable.cacheKey();
            //获得参数的名字
            ParameterNameDiscoverer parameterNameDiscoverer=new DefaultParameterNameDiscoverer();
            String[] names=parameterNameDiscoverer.getParameterNames(method);
            //获得目标方法上的参数的值
            Object[] args=proceedingJoinPoint.getArgs();
            //解析器对象
            ExpressionParser expressionParser=new SpelExpressionParser();
            Expression expression=expressionParser.parseExpression(cacheKeyPress);
            //上下文对象，将参数的名字和参数的值保存到上下文中，表达式从上下文中获取参数的值
            EvaluationContext context=new StandardEvaluationContext();
            for (int i = 0; i < names.length; i++) {
                String name=names[i];
                Object arg=args[i];
                context.setVariable(name,arg);
            }
            //获得表达式的值
            String spelValue=expression.getValue(context,String.class);
            String key=myCacheable.cacheName()+":"+spelValue;
            if (redisUtil.hasKey(key)){
                Object value=redisUtil.get(key);
                return value;
            }
            getValue(proceedingJoinPoint, myCacheable, key);
        }

        //2 如果缓存中没有数据，将方法的返回值放入到缓存中，将结果返回
        return null;
    }

    private Object getValue(ProceedingJoinPoint proceedingJoinPoint, MyCacheable myCacheable, String key) throws Throwable {
        String lockKey= key +":lock";
        RReadWriteLock readWriteLock=redissonClient.getReadWriteLock(key);
        RLock rLock=readWriteLock.writeLock();
        rLock.lock();
        try {
            if (redisUtil.hasKey(key)){
                return redisUtil.get(key);
            }
            //调用目标方法，获得目标方法的返回值
            Object value= proceedingJoinPoint.proceed();
            //将结果保存到缓存中
            redisUtil.set(key,value, myCacheable.time(), myCacheable.timeUnit());
            return value;
        }finally {
            rLock.unlock();
        }
    }
}
